﻿#include "BCDevice.h"

#include <QDebug>
#include <QAudioInput>
#include <QAudioOutput>
#include <QThread>
#include <QTimer>
#include "AudioOutputThread.h"

#include "QueueBuffer.h"
#include "LoopBuffer.h"

#include "VideoSurface.h"
#include "generateimage.h"

#include "FormatConvert.h"
#include "Mp3Decode.h"

extern "C"
{
#include <libavformat/avformat.h>
};

BCDevice * BCDevice::instance = nullptr;

BCDevice * BCDevice::getInstance(){
    if (!instance) {
        instance = new BCDevice;
    }
    return instance;
}

BCDevice::BCDevice(QObject *parent) : QObject(parent)
{
    getLoudspeakers();
    getMicrophones();
    getCameras();
    m_buffer = new LoopBuffer(1024*1024);
    //    s_buffer->setWriteMode(LoopBuffer::WriteUnlimited);
    m_buffer->open(QIODevice::ReadWrite);

    // init camera
    m_surface = new VideoSurface;
    m_generate = new GenerateImage;
}

QStringList BCDevice::getMicrophones()
{
    QStringList result;

    s_micDevices.clear();
    for (auto device : QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) {
        if (device.supportedSampleRates().contains(8000)) {
            s_micDevices.push_back(device);
            result.push_back(device.deviceName());
        }
    }

    return result;
}

QStringList BCDevice::getLoudspeakers()
{
    QStringList loudSpeakers;
    s_loudSpeakerDevices.clear();

    for (auto device : QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) {
        if (device.supportedSampleRates().contains(8000)) {
            s_loudSpeakerDevices.push_back(device);
            loudSpeakers.push_back(device.deviceName());
        }
    }

    return loudSpeakers;
}

QStringList BCDevice::getCameras()
{
    QStringList cameras;
    s_cameras.clear();

    s_cameras = QCameraInfo::availableCameras();
    for (auto camera : s_cameras) {
        cameras.push_back(camera.description());
    }
    return cameras;
}

GenerateImage * BCDevice::getGenerate()
{
    return m_generate;
}

int BCDevice::currentThreadId()
{
    return (int)QThread::currentThreadId();
}

void BCDevice::dumpInfo()
{
    for (auto device:QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) {
        qDebug() << "input" << device.deviceName() << device.supportedCodecs() << device.supportedChannelCounts() << device.supportedSampleRates() << device.supportedSampleSizes();
    }


    for (auto device:QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) {
        qDebug() << "output" << device.deviceName() <<  device.supportedCodecs() << device.supportedChannelCounts() << device.supportedSampleRates() << device.supportedSampleSizes();
    }
}

void BCDevice::devUpdate()
{
    getCameras();
    getMicrophones();
    getLoudspeakers();
}

void BCDevice::setAudioParam(int sampleRate,int channels,int sampleBites)
{
    s_sampleRate = sampleRate;
    s_channels = channels;
    s_sampleBits = sampleBites;
}

bool BCDevice::startCamera(int index)
{
    if (s_cameras.isEmpty())
        return false;

    s_cameraIndex = index;
    QCameraInfo info = s_cameras[s_cameraIndex];

    qDebug() << u8"摄像头开启" << s_cameraIndex;

    if (m_camera) {
        m_camera->stop();
        m_camera->deleteLater();
        m_camera = nullptr;
    }

    m_camera = new QCamera(info,0);
    connect(m_surface,&VideoSurface::generateFrame,[this](QVideoFrame frame){
        if(!frame.isMapped()){
            //这里必须判断数据是否可取，不可取需要获取cpu并把数据拷贝下来，QImage和QVideoFrame的格式同名，每秒大概产生20次回调
            frame.map(QAbstractVideoBuffer::ReadOnly);
            //frame的大小会多出一个32字节，根据事实情况酌情考虑（一般没问题）
            AVFrame *ffmpegFrame = FormatConvert::convertToFrame(frame,QVideoFrame::Format_YUV420P);
            if (m_generate)
                m_generate->renderVideo(ffmpegFrame);
            av_frame_free(&ffmpegFrame);
        }else{
        }
    });

    // 设置摄像头参数
    QCameraViewfinderSettings viewfinderSettings;
    viewfinderSettings.setResolution(1024, 768);
    viewfinderSettings.setMinimumFrameRate(30);
    viewfinderSettings.setMaximumFrameRate(30);
    //    viewfinderSettings.setPixelFormat(QVideoFrame::Format_Jpeg);
    //    camera->setViewfinderSettings(viewfinderSettings);

    m_surface->setSource(m_camera);

    m_camera->start();

    return true;
}

bool BCDevice::startMicrophone(int index)
{
    if (s_micDevices.isEmpty())
        return false;

    s_micIndex = index;

    if (s_audioInput) {
        s_audioInput->stop();
        s_audioInput->deleteLater();
        s_audioInput = nullptr;
    }


    QAudioDeviceInfo microphone = s_micDevices.at(s_micIndex);

    qDebug() << u8"麦克风开始" << s_micIndex << microphone.deviceName();

    QAudioFormat format;
    format.setSampleRate(s_sampleRate);
    format.setChannelCount(s_channels);
    format.setSampleSize(s_sampleBits);
    format.setSampleType(QAudioFormat::SignedInt);

    format = microphone.nearestFormat(format);

    s_audioInput = new QAudioInput(format,0);

    connect(s_audioInput,&QAudioInput::stateChanged,[this](QAudio::State state){
        switch (state) {
        case QAudio::IdleState:
            qDebug() << "input idle";
            break;
        case QAudio::ActiveState:
            qDebug() << "input active";
            break;
        case QAudio::SuspendedState:
            qDebug() << "input suspend";
            break;
        case QAudio::StoppedState:
            qDebug() << "input stop";
            break;
        default:
            break;
        }
    });

    s_audioInput->start(m_buffer);
    return true;
}

bool BCDevice::startLoudspeaker(int index)
{
    if (s_loudSpeakerDevices.empty())
        return false;

    s_loudspeakerIndex = index;

    if (s_audioOutput) {
        s_audioOutput->stop();
        s_audioOutput->deleteLater();
        s_audioOutput = nullptr;
    }

    QAudioDeviceInfo loudspeaker = s_loudSpeakerDevices.at(s_loudspeakerIndex);

    qDebug() << u8"扬声器开始" << s_loudspeakerIndex << loudspeaker.deviceName();

    QAudioFormat format;
    format.setSampleRate(s_sampleRate);
    format.setChannelCount(s_channels);
    format.setSampleSize(s_sampleBits);
    //    format.setSampleType(QAudioFormat::SignedInt);

    format = loudspeaker.nearestFormat(format);

    s_audioOutput = new QAudioOutput(format,0);

    connect(s_audioOutput,&QAudioOutput::stateChanged,[this](QAudio::State state){
        switch (state) {
        case QAudio::IdleState:
            qDebug() << "output idle";
            break;
        case QAudio::ActiveState:
            qDebug() << "output active";
            break;

        case QAudio::SuspendedState:
            qDebug() << "output suspend";
            break;
        case QAudio::StoppedState:
            qDebug() << "output stop";
            break;
        default:
            break;
        }
    });

    s_audioOutput->start(m_buffer);

    return true;
}

bool BCDevice::stopCamera(int index)
{
    s_cameraIndex = -1;
    if (m_camera)
        m_camera->stop();
    return true;
}

/* 当任一个设备停止时，都应该停止buffer，要么buffer就设计为循环 */

bool BCDevice::stopMicrophone(int index)
{
    s_micIndex = -1;

    if (s_audioInput)
        s_audioInput->stop();
    return true;
}

bool BCDevice::stopLoudspeaker(int index)
{
    s_loudspeakerIndex = -1;
    if (s_audioOutput)
        s_audioOutput->stop();
    return true;
}

bool BCDevice::stopAll()
{
    stopCamera(0);
    stopLoudspeaker(0);
    stopMicrophone(0);
    return true;
}

bool BCDevice::playMp3(const QString &file)
{
    if (m_mp3IsPlaying)
        return false;

    m_mp3IsPlaying = true;
    int dstSampleRate = 44100;
    int dstSampleBits = 16;

    Mp3Decode *decode = new Mp3Decode(file,0);

    if (!m_mp3Buffer) {
        m_mp3Buffer = new LoopBuffer(LoopBuffer::getAudioSuggestSize(dstSampleRate,2,dstSampleBits),true);
        m_mp3Buffer->open(QIODevice::ReadWrite);
        m_mp3Buffer->setWriteMode(LoopBuffer::WriteRemainLimited);
    }

    decode->setParam(dstSampleRate,m_mp3Buffer);

    /**
     * 1. 空间有限，无限循环：数据会很快覆盖写完，由于空间比较少，很快读完了，那么就只能返回空数据了。而且在写入过程中，数据会被覆盖，造成数据不连续
     * 2. 空间有限，余量限制：正常，没有采用信号量模式.如果使用信号量，应该多些字节
     */

    setAudioParam(dstSampleRate,2,dstSampleBits);

    m_mp3DecodeThread = new QThread;
    decode->moveToThread(m_mp3DecodeThread);
    connect(this,&BCDevice::sgStopMp3,decode,&Mp3Decode::stop);
    connect(m_mp3DecodeThread,&QThread::finished,decode,&Mp3Decode::deleteLater);

    m_mp3DecodeThread->start();

    /* 启动解码器 */
    QTimer *timer = new QTimer;
    timer->setSingleShot(true);
    connect(timer,&QTimer::timeout,decode,&Mp3Decode::run);
    timer->start(0);


    /* 启动扬声器 */
    {
        if (s_loudSpeakerDevices.empty())
            return false;
        QAudioDeviceInfo loudspeaker = s_loudSpeakerDevices.at(0);

        qDebug() << u8"扬声器开始" << 0 << loudspeaker.deviceName();

        QAudioFormat format;
        format.setSampleRate(dstSampleRate);
        format.setChannelCount(2);
        format.setSampleSize(dstSampleBits);
        //        format.setSampleType(QAudioFormat::SignedInt);

        format = loudspeaker.nearestFormat(format);

        AudioOutputThread *audio = new AudioOutputThread(format,0);
        audio->setBuffer(m_mp3Buffer);

        m_audioOutputThread = new QThread;
        audio->moveToThread(m_audioOutputThread);
        connect(this,&BCDevice::sgStopMp3,audio,&AudioOutputThread::stop,Qt::QueuedConnection);
        connect(m_audioOutputThread,&QThread::finished,audio,&AudioOutputThread::deleteLater);

        m_audioOutputThread->start();

        QTimer *outputTimer = new QTimer;
        outputTimer->setSingleShot(true);
        connect(outputTimer,&QTimer::timeout,audio,&AudioOutputThread::run);
        outputTimer->start(0);
    }

    return true;
}

bool BCDevice::stopMp3()
{
    emit sgStopMp3();
    if (m_audioOutputThread && m_mp3DecodeThread) {
        m_audioOutputThread->quit();
        m_audioOutputThread->wait();
        m_audioOutputThread->deleteLater();
        m_audioOutputThread = nullptr;

        m_mp3DecodeThread->quit();
        m_mp3DecodeThread->wait();
        m_mp3DecodeThread->deleteLater();
        m_mp3DecodeThread = nullptr;
    }

    if (m_mp3Buffer) {
        m_mp3Buffer->clear();
        m_mp3Buffer->deleteLater();
        m_mp3Buffer = nullptr;
    }

    m_mp3IsPlaying = false;

    return true;
}
